/*!
 *
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 *
 * Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
 *
 */

package org.pentaho.platform.uifoundation.chart;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.Node;
import org.pentaho.commons.connection.DataUtilities;
import org.pentaho.commons.connection.IPentahoConnection;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.platform.api.engine.ILogger;
import org.pentaho.platform.api.engine.IMessageFormatter;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IPentahoRequestContext;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.platform.engine.core.system.PentahoRequestContextHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.ActionSequenceJCRHelper;
import org.pentaho.platform.engine.services.connection.PentahoConnectionFactory;
import org.pentaho.platform.engine.services.runtime.TemplateUtil;
import org.pentaho.platform.uifoundation.messages.Messages;
import org.pentaho.platform.util.logging.Logger;
import org.pentaho.platform.util.messages.LocaleHelper;
import org.pentaho.platform.util.messages.MessagesBase;
import org.pentaho.platform.util.web.SimpleUrlFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * This class provides wrapper functions to make it easier to execute action sequences and generate a widget.
 */
@Deprecated // BISERVER-12899
public class ChartHelper {

  private static void deprecateWarning() {
    String key = "PentahoSystem.WARN_DEPRECATED_CLASS"; //$NON-NLS-1$
    MessagesBase messages = org.pentaho.platform.engine.core.messages.Messages.getInstance();
    String message = messages.getErrorString( key, ChartHelper.class.getCanonicalName() );
    String stackTrace = StringUtils.join( Thread.currentThread().getStackTrace(), "\n\t" ); //$NON-NLS-1$
    Logger.warn( ChartHelper.class, message + "\n\t" + stackTrace ); //$NON-NLS-1$
  }

  /**
   * doChart generates the images and html necessary to render various charts within a web page.
   * 
   * @param actionPath
   *          full path including the name of the action sequence or resource
   * @param parameterProvider
   *          the collection of parameters to customize the chart
   * @param outputStream
   *          the output string buffer for the content
   * @param userSession
   *          the user session object
   * @param messages
   *          a collection to store error and logging messages
   * @param logger
   *          logging object
   * 
   * @return true if successful
   */
  @Deprecated
  public static boolean doChart( final String actionPath, final IParameterProvider parameterProvider,
      final StringBuffer outputStream, final IPentahoSession userSession, final ArrayList messages, ILogger logger ) {
    deprecateWarning();

    boolean result = true;
    String content = null;
    StringBuffer messageBuffer = new StringBuffer();

    if ( logger == null ) {
      // No logger? The usersession extends ILogger, use it for logging
      logger = userSession;
    }

    // Retrieve all parameters from parameter provider

    String outerParams = parameterProvider.getStringParameter( "outer-params", null ); //$NON-NLS-1$
    String innerParam = parameterProvider.getStringParameter( "inner-param", null ); //$NON-NLS-1$

    String urlDrillTemplate = parameterProvider.getStringParameter( "drill-url", null ); //$NON-NLS-1$
    String imageUrl = parameterProvider.getStringParameter( "image-url", null ); //$NON-NLS-1$

    // Very likely null; allow API users to continue to pass the dial value via parameters
    String dialValue = parameterProvider.getStringParameter( "value", null ); //$NON-NLS-1$
    IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext();
    if ( imageUrl == null ) {
      imageUrl = requestContext.getContextPath(); //$NON-NLS-1$
    }

    if ( urlDrillTemplate == null ) {
      urlDrillTemplate = ""; //$NON-NLS-1$
    }

    int width = (int) parameterProvider.getLongParameter( "image-width", 150 ); //$NON-NLS-1$
    int height = (int) parameterProvider.getLongParameter( "image-height", 150 ); //$NON-NLS-1$

    SimpleUrlFactory urlFactory = new SimpleUrlFactory( urlDrillTemplate );

    // Determine the type of chart we are building; these values can come from the chart xml definition, or
    // from the parameter provider. Try the parameter provider first, for performance reasons.

    String chartTypeStr = parameterProvider.getStringParameter( ChartDefinition.TYPE_NODE_NAME, null );
    String datasetType = ChartDefinition.CATEGORY_DATASET_STR;
    if ( ( chartTypeStr == null ) || ( chartTypeStr.length() == 0 ) ) {

      try {
        // attempt to get the chart type and possibly data type from the xml doc
        ActionSequenceJCRHelper jcrHelper = new ActionSequenceJCRHelper( userSession );
        Document chartDefinition = jcrHelper.getSolutionDocument( actionPath, RepositoryFilePermission.READ );
        Node chartAttributes = chartDefinition.selectSingleNode( "//" + AbstractChartComponent.CHART_NODE_NAME ); //$NON-NLS-1$
        chartTypeStr = chartAttributes.selectSingleNode( ChartDefinition.TYPE_NODE_NAME ).getText();
        Node datasetTypeNode = chartAttributes.selectSingleNode( ChartDefinition.DATASET_TYPE_NODE_NAME );
        if ( datasetTypeNode != null ) {
          datasetType = datasetTypeNode.getText();
        }

      } catch ( Exception e ) {

        logger.error(
            Messages.getInstance().getErrorString( "ChartHelper.ERROR_0001_IO_PROBLEM_GETTING_CHART_TYPE" ), e ); //$NON-NLS-1$
        PentahoSystem
            .get( IMessageFormatter.class, userSession )
            .formatErrorMessage(
                "text/html", Messages.getInstance().getString( "ChartHelper.ERROR_0001_IO_PROBLEM_GETTING_CHART_TYPE" ), messages, messageBuffer ); //$NON-NLS-1$ //$NON-NLS-2$
        content = messageBuffer.toString();
        result = false;
      }
    }

    // Check again - do we have a chart type now? If not, bail out, we have no idea what to try to generate
    if ( ( chartTypeStr == null ) || ( chartTypeStr.length() == 0 ) ) {

      logger.error( Messages.getInstance().getString( "ChartHelper.ERROR_0002_COULD_NOT_DETERMINE_CHART_TYPE" ) ); //$NON-NLS-1$
      PentahoSystem
          .get( IMessageFormatter.class, userSession )
          .formatErrorMessage(
              "text/html", Messages.getInstance().getString( "ChartHelper.ERROR_0002_COULD_NOT_DETERMINE_CHART_TYPE" ), messages, messageBuffer ); //$NON-NLS-1$ //$NON-NLS-2$
      content = messageBuffer.toString();
      result = false;
    }

    if ( !result ) {
      outputStream.append( content );
      return result;
    }

    int chartType = JFreeChartEngine.getChartType( chartTypeStr );
    AbstractJFreeChartComponent chartComponent = null;

    try {
      // Some charts are determined by the dataset that is passed in; check these first...
      if ( datasetType.equalsIgnoreCase( ChartDefinition.TIME_SERIES_COLLECTION_STR ) ) {
        chartComponent =
            new TimeSeriesCollectionChartComponent( chartType, actionPath, width, height, urlFactory, messages );
      } else if ( datasetType.equalsIgnoreCase( ChartDefinition.XY_SERIES_COLLECTION_STR ) ) {
        chartComponent =
            new XYSeriesCollectionChartComponent( chartType, actionPath, width, height, urlFactory, messages );
      } else if ( datasetType.equalsIgnoreCase( ChartDefinition.XYZ_SERIES_COLLECTION_STR ) ) {
        chartComponent =
            new XYZSeriesCollectionChartComponent( chartType, actionPath, width, height, urlFactory, messages );
      }

      // Didn't find a dataset, so try to create the component based on chart type.
      if ( chartComponent == null ) {
        switch ( chartType ) {
          case JFreeChartEngine.BAR_CHART_TYPE:
          case JFreeChartEngine.AREA_CHART_TYPE:
          case JFreeChartEngine.BAR_LINE_CHART_TYPE:
          case JFreeChartEngine.LINE_CHART_TYPE:
          case JFreeChartEngine.DIFFERENCE_CHART_TYPE:
          case JFreeChartEngine.DOT_CHART_TYPE:
          case JFreeChartEngine.STEP_AREA_CHART_TYPE:
          case JFreeChartEngine.STEP_CHART_TYPE:
          case JFreeChartEngine.PIE_GRID_CHART_TYPE:

            chartComponent =
                new CategoryDatasetChartComponent( chartType, actionPath, width, height, urlFactory, messages );
            break;

          case JFreeChartEngine.PIE_CHART_TYPE:

            chartComponent = new PieDatasetChartComponent( chartType, actionPath, width, height, urlFactory, messages );
            break;

          case JFreeChartEngine.DIAL_CHART_TYPE:

            chartComponent = new DialChartComponent( chartType, actionPath, width, height, urlFactory, messages );
            if ( dialValue != null ) {
              Number numericDialValue =
                  DataUtilities.toNumber( dialValue, LocaleHelper.getCurrencyFormat(), LocaleHelper.getNumberFormat() );
              ( (DialChartComponent) chartComponent ).setValue( numericDialValue.doubleValue() );
            }
            break;

          case JFreeChartEngine.BUBBLE_CHART_TYPE:

            chartComponent =
                new XYZSeriesCollectionChartComponent( chartType, actionPath, width, height, urlFactory, messages );
            break;

          case JFreeChartEngine.UNDEFINED_CHART_TYPE:
          default:
            // Unsupported chart type, bail out
            logger.error( Messages.getInstance().getString(
                "ChartHelper.ERROR_0003_INVALID_CHART_TYPE", chartTypeStr, Integer.toString( chartType ) ) ); //$NON-NLS-1$
            PentahoSystem.get( IMessageFormatter.class, userSession ).formatErrorMessage(
                "text/html", Messages.getInstance().getString( "ChartHelper.ERROR_0003_INVALID_CHART_TYPE", //$NON-NLS-1$ //$NON-NLS-2$
                    chartTypeStr, Integer.toString( chartType ) ), messages, messageBuffer );
            content = messageBuffer.toString();
            result = false;

        }
      }

      if ( result && ( chartComponent != null ) ) {
        try {

          chartComponent.setLoggingLevel( logger.getLoggingLevel() );
          chartComponent.validate( userSession, null );
          chartComponent.setDataAction( actionPath );
          chartComponent.setUrlTemplate( urlDrillTemplate );

          String seriesName = parameterProvider.getStringParameter( "series-name", null ); //$NON-NLS-1$
          if ( chartComponent instanceof CategoryDatasetChartComponent ) {
            ( (CategoryDatasetChartComponent) chartComponent ).setSeriesName( seriesName );
          }

          // WARNING!!! This is an atypical way to access data for the chart... these parameters and their
          // usage are undocumented, and only left in here to support older solutions that may be using them.
          // *************** START QUESTIONABLE CODE ********************************************************

          String connectionName = parameterProvider.getStringParameter( "connection", null ); //$NON-NLS-1$
          String query = parameterProvider.getStringParameter( "query", null ); //$NON-NLS-1$
          String dataAction = parameterProvider.getStringParameter( "data-process", null ); //$NON-NLS-1$

          IPentahoConnection connection = null;
          try {
            chartComponent.setParamName( innerParam );
            chartComponent.setParameterProvider( IParameterProvider.SCOPE_REQUEST, parameterProvider );
            if ( ( connectionName != null ) && ( query != null ) ) {
              // connection = new SQLConnection(connectionName, logger)
              // TODO support non-SQL data sources. Much easier now using the factory
              connection =
                  PentahoConnectionFactory.getConnection( IPentahoConnection.SQL_DATASOURCE, connectionName,
                      userSession, logger );

              try {
                query =
                    TemplateUtil.applyTemplate( query, TemplateUtil.parametersToProperties( parameterProvider ), null );
                IPentahoResultSet results = connection.executeQuery( query );
                chartComponent.setValues( results );
              } finally {
                boolean ignored = true;
              }

              chartComponent.setUrlTemplate( urlDrillTemplate );
              if ( outerParams != null ) {
                StringTokenizer tokenizer = new StringTokenizer( outerParams, ";" ); //$NON-NLS-1$
                while ( tokenizer.hasMoreTokens() ) {
                  chartComponent.addOuterParamName( tokenizer.nextToken() );
                }
              }
            } else if ( dataAction != null ) {
              chartComponent.setDataAction( dataAction );
            }
            // ***************** END QUESTIONABLE CODE ********************************************************

            content = chartComponent.getContent( "text/html" ); //$NON-NLS-1$

          } finally {
            if ( connection != null ) {
              connection.close();
            }
          }

        } catch ( Throwable e ) {
          logger.error( Messages.getInstance().getErrorString( "Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET" ), e ); //$NON-NLS-1$
        }
      } // end of if(result)

      try {
        if ( content == null ) {
          PentahoSystem
              .get( IMessageFormatter.class, userSession )
              .formatErrorMessage(
                  "text/html", Messages.getInstance().getErrorString( "Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET" ), messages, messageBuffer ); //$NON-NLS-1$ //$NON-NLS-2$
          content = messageBuffer.toString();
          result = false;
        }
        outputStream.append( content );
      } catch ( Exception e ) {
        logger.error( Messages.getInstance().getErrorString( "Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET" ), e ); //$NON-NLS-1$
      }

    } finally {
      if ( chartComponent != null ) {
        chartComponent.dispose();
      }
    }
    return result;
  }

  /**
   * doPieChart generates the images and html necessary to render pie charts. It provides a simple wrapper around
   * the class org.pentaho.ui.component.charting.PieDatasetChartComponent
   * 
   * @param solutionName
   *          the solution name
   * @param actionPath
   *          the action path
   * @param chartName
   *          the xml file describing the chart
   * @param parameterProvider
   *          the collection of parameters to customize the chart
   * @param outputStream
   *          the output string buffer for the content
   * @param userSession
   *          the user session object
   * @param messages
   *          a collection to store error and logging messages
   * @param logger
   *          logging object
   * 
   * @return true if successful
   * @deprecated use doChart instead
   */
  @Deprecated
  public static boolean
  doPieChart( final String actionPath, final IParameterProvider parameterProvider, final StringBuffer outputStream,
        final IPentahoSession userSession, final ArrayList messages, final ILogger logger ) {
    deprecateWarning();

    boolean result = true;
    String outerParams = parameterProvider.getStringParameter( "outer-params", null ); //$NON-NLS-1$
    String innerParam = parameterProvider.getStringParameter( "inner-param", null ); //$NON-NLS-1$

    String urlDrillTemplate = parameterProvider.getStringParameter( "drill-url", null ); //$NON-NLS-1$
    String imageUrl = parameterProvider.getStringParameter( "image-url", null ); //$NON-NLS-1$
    IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext();
    if ( imageUrl == null ) {
      imageUrl = requestContext.getContextPath(); //$NON-NLS-1$
    }

    if ( urlDrillTemplate == null ) {
      urlDrillTemplate = ""; //$NON-NLS-1$
    }

    int width = (int) parameterProvider.getLongParameter( "image-width", 150 ); //$NON-NLS-1$
    int height = (int) parameterProvider.getLongParameter( "image-height", 150 ); //$NON-NLS-1$

    SimpleUrlFactory urlFactory = new SimpleUrlFactory( urlDrillTemplate );

    PieDatasetChartComponent chartComponent = null;
    try {
      chartComponent =
          new PieDatasetChartComponent( JFreeChartEngine.PIE_CHART_TYPE, actionPath, width, height, urlFactory,
              messages );
      if ( logger != null ) {
        chartComponent.setLoggingLevel( logger.getLoggingLevel() );
      }
      chartComponent.validate( userSession, null );
      chartComponent.setUrlTemplate( urlDrillTemplate );
      if ( outerParams != null ) {
        StringTokenizer tokenizer = new StringTokenizer( outerParams, ";" ); //$NON-NLS-1$
        while ( tokenizer.hasMoreTokens() ) {
          chartComponent.addOuterParamName( tokenizer.nextToken() );
        }
      }
      chartComponent.setParamName( innerParam );

      chartComponent.setDataAction( actionPath );

      chartComponent.setParameterProvider( IParameterProvider.SCOPE_REQUEST, parameterProvider );

      String content = chartComponent.getContent( "text/html" ); //$NON-NLS-1$

      if ( ( content == null ) || content.equals( "" ) ) { //$NON-NLS-1$
        content = "&nbsp;"; //$NON-NLS-1$
      }
      outputStream.append( content );

    } finally {
      if ( chartComponent != null ) {
        chartComponent.dispose();
      }
    }
    return result;

  }

  /**
   * doDial generates the images and html necessary to render dials. It provides a simple wrapper around the class
   * org.pentaho.ui.component.DashboardWidgetComponent
   * 
   * @param solutionName
   *          the solution name
   * @param actionPath
   *          the action path
   * @param chartName
   *          the xml file describing the chart
   * @param parameterProvider
   *          the collection of parameters to customize the chart
   * @param outputStream
   *          the output string buffer for the content
   * @param userSession
   *          the user session object
   * @param messages
   *          a collection to store error and logging messages
   * @param logger
   *          logging object
   * 
   * @return true if successful
   * @deprecated use doChart() instead
   */
  @Deprecated
  public static boolean doDial( final String solutionName, final String actionPath, final String chartName,
      final IParameterProvider parameterProvider, final StringBuffer outputStream, final IPentahoSession userSession,
      final ArrayList messages, final ILogger logger ) {
    deprecateWarning();

    boolean result = true;
    String linkUrl = parameterProvider.getStringParameter( "drill-url", null ); //$NON-NLS-1$
    String imageUrl = parameterProvider.getStringParameter( "image-url", null ); //$NON-NLS-1$
    IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext();
    if ( imageUrl == null ) {
      imageUrl = requestContext.getContextPath(); //$NON-NLS-1$
    }

    if ( linkUrl == null ) {
      linkUrl = ""; //$NON-NLS-1$
    }

    int width = (int) parameterProvider.getLongParameter( "image-width", 150 ); //$NON-NLS-1$
    int height = (int) parameterProvider.getLongParameter( "image-height", 150 ); //$NON-NLS-1$

    SimpleUrlFactory urlFactory = new SimpleUrlFactory( linkUrl );

    DashboardWidgetComponent widget = null;
    try {
      widget =
          new DashboardWidgetComponent( DashboardWidgetComponent.TYPE_DIAL, solutionName + File.separator + actionPath
              + File.separator + chartName, width, height, urlFactory, messages );
      if ( logger != null ) {
        widget.setLoggingLevel( logger.getLoggingLevel() );
      }
      widget.validate( userSession, null );

      widget.setParameterProvider( IParameterProvider.SCOPE_REQUEST, parameterProvider );

      String value = parameterProvider.getStringParameter( "value", "0" ); //$NON-NLS-1$//$NON-NLS-2$
      Number numericValue =
          DataUtilities.toNumber( value, LocaleHelper.getCurrencyFormat(), LocaleHelper.getNumberFormat() );

      widget.setValue( numericValue.doubleValue() );

      String title = parameterProvider.getStringParameter( "title", "" ); //$NON-NLS-1$ //$NON-NLS-2$
      widget.setTitle( title );

      String content = widget.getContent( "text/html" ); //$NON-NLS-1$

      if ( content == null ) {
        StringBuffer buffer = new StringBuffer();
        PentahoSystem
            .get( IMessageFormatter.class, userSession )
            .formatErrorMessage(
                "text/html", Messages.getInstance().getString( "Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET" ), messages, buffer ); //$NON-NLS-1$ //$NON-NLS-2$
        content = buffer.toString();
        result = false;
      }

      if ( ( content == null ) || content.equals( "" ) ) { //$NON-NLS-1$
        content = "&nbsp;"; //$NON-NLS-1$
      }
      outputStream.append( content );

    } finally {
      if ( widget != null ) {
        widget.dispose();
      }
    }
    return result;

  }

}
